// pointsprites.c + my own modification
/*
 * This is a modified point sprites example from the book OpenGLSuperBible. The 
 * original program was written by Richard S. Wright Jr.
 *  
 * I took the texture generator and some ideas for the camera movement from the 
 * nbody example of the CUDA SDK (renderparticles.cpp). 
 * 
 * I am not aware of the licenses involved in these codes, but I think you may 
 * use it as it is without expecting any support. Also, you cannot sell this
 * binary or the source code.
 * 
 * Hope you enjoy.
 * 
 * Pedro Fluxá R.
 *
 */

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <common.h>

// Global pointer to coordinates
float *vSmallStars;

// Default screen size
#define SCREEN_X    1000
#define SCREEN_Y     800 

// Texture objects
GLuint  textureObjects[1];

// Input data file
FILE *data;

long snapshot = 0;

struct camera
{
	
	float   spin[3];
	float  trans[3];
	float lastxy[2];
	
	float inertia  ;
};

struct camera CAM;

// Create an array of boolean values of length 256 (0-255)
bool* keyStates = new bool[256];   

void loadSnapshot( long  i, FILE *D )
{
	char foo[1024];
	
	//char name[] = "../output/coord.txt";
	
	//printf( "%s\n", name );
	
	//FILE *data;
	//data = fopen( name, "r" );
	
	if( D == NULL )
	{
		printf( "CANNOT OPEN FILE! EXITING\n" );
		exit(-4);
	}
	
	//Load correct snapshot
	//for( int l = 0; l < i * SMALL_STARS; l++)
	//    fgets( foo, 1024, data );
	
	if( feof(D) )
		fseek( D, 0, SEEK_SET );
	
	float x,y,z,fooo; 
    // Populate star list
    
    for( double d = 0; d < 1e7; d++ ){}
    for(i = 0; i < SMALL_STARS && !feof(D); i++)
    {
		fscanf( D, "%f%f%f%f%f%f%f\n", &fooo, &x, &y, &z,&fooo,&fooo,&fooo);
        vSmallStars[i][0] = x;
        vSmallStars[i][1] = y;
        vSmallStars[i][2] = z;
        
        //printf( "%f %f %f\n", x,y,z);
    }
    
    //fclose(data);
}

void keyPressed (unsigned char key, int x, int y) 
{  
	keyStates[key] = true; // Set the state of the current key to pressed  
}  
  
void keyUp (unsigned char key, int x, int y) 
{  
	keyStates[key] = false; // Set the state of the current key to not pressed  
}  

void keyOperations (void) 
{  
	
	if (keyStates['n']) 
	{
		snapshot++;
		if( snapshot > 1000 )
		snapshot = 1000;
	
		loadSnapshot( snapshot, data );
	}
	
	if (keyStates['b']) 
	{
		snapshot--;
		if( snapshot < 0 )
			snapshot = 0;
	
		loadSnapshot( snapshot, data );
	}   
	
	if (keyStates['w']) 
	{
		CAM.trans[2] += 0.0008;
	}   
	
	if (keyStates['s']) 
	{
		CAM.trans[2] -= 0.0008;
	}   
    
}

void cameraMovementFunction(int x, int y) 
{

	GLuint specialKey = glutGetModifiers();

	if (specialKey == GLUT_ACTIVE_SHIFT) {

		CAM.spin[0] += 360.0 * ( x - CAM.lastxy[0] )/800.0;
		CAM.spin[1] += 360.0 * ( y - CAM.lastxy[1] )/800.0;
	}
	
	if (specialKey == GLUT_ACTIVE_ALT) {
	
		CAM.trans[0] += 0.005*( CAM.lastxy[0] - x ); 
		CAM.trans[1] += 0.005*( CAM.lastxy[1] - y );
	}
	
	CAM.lastxy[0] = x;
	CAM.lastxy[1] = y;
	
}

void zoomFunction(int button, int state, int x, int y)
{
	//Zoom in
    if ( button == 3     )
	{
		CAM.trans[2] += 0.08;
	}
	
	//Zoom out
	else if( button == 4 )
	{
		CAM.trans[2] -= 0.08;
	}
}

void renderScene(void)
{
    keyOperations();
    
    long i;
          
    // Clear the window
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
         
    // Everything is black
    glColor3f(0.0f, 0.0f, 0.0f);
    
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_BLEND);
    glDepthMask(GL_FALSE);
    
    glEnable(GL_POINT_SPRITE);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, textureObjects[0]);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glPushMatrix();
		glTranslatef( -CAM.trans[0], 
					   CAM.trans[1], 
					   CAM.trans[2] );
					   
		glRotatef   (  CAM.spin[0], 0,1,0 );
		glRotatef   (  CAM.spin[1], 1,0,0 );
    	
    	glPointSize(15.8f);
		glBegin(GL_POINTS);
			glVertex3fv(vSmallStars[0]);
		glEnd();
		
		
		glPointSize(5.8f);
		glBegin(GL_POINTS);
			for(i = 1; i < SMALL_STARS; i++)
				glVertex3fv(vSmallStars[i]);
				
		glEnd();
	glPopMatrix();
	
    glutSwapBuffers();
    glutPostRedisplay();
}

void idle( void )
{
	glutPostRedisplay();
}

float evalHermite(float pA, float pB, float vA, float vB, float u)
{
    float u2=(u*u), u3=u2*u;
    float B0 = 2*u3 - 3*u2 + 1;
    float B1 = -2*u3 + 3*u2;
    float B2 = u3 - 2*u2 + u;
    float B3 = u3 - u;
    return( B0*pA + B1*pB + B2*vA + B3*vB );
}

unsigned char* createGaussianMap(int N)
{
    float *M = new float[2*N*N];
    unsigned char *B = new unsigned char[4*N*N];
    float X,Y,Y2,Dist;
    float Incr = 2.0f/N;
    int i=0;  
    int j = 0;
    Y = -1.0f;
    //float mmax = 0;
    for (int y=0; y<N; y++, Y+=Incr)
    {
        Y2=Y*Y;
        X = -1.0f;
        for (int x=0; x<N; x++, X+=Incr, i+=2, j+=4)
        {
            Dist = (float)sqrtf(X*X+Y2);
            if (Dist>1) Dist=1;
            M[i+1] = M[i] = evalHermite(1.0f,0,0,0,Dist);
            B[j+3] = B[j+2] = B[j+1] = B[j] = (unsigned char)(M[i] * 255);
        }
    }
    delete [] M;
    return(B);
}

void initGL()
{
    loadSnapshot( 0, data );
    
    int i;
   
    // Black background
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f );

    // Set drawing color to white
	glColor4f(1.0f,1.0f,1.0f,1.0f);

	// Load our textures
	int resolution = 32;
    glGenTextures(2, textureObjects);
    GLbyte *pBytes;
    
    GLint iWidth, iHeight, iComponents;
    GLenum eFormat;
     
    glBindTexture(GL_TEXTURE_2D, textureObjects[0]);
       
    // Load this texture map
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    pBytes = (GLbyte *)createGaussianMap(resolution);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, resolution, resolution, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBytes);
    free(pBytes);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

    // Point Sprites enabled by default
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);

    glEnable(GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER,0.1f);

	glDisable(GL_LINE_SMOOTH);
	glDisable(GL_POINT_SMOOTH);
	glDisable(GL_POLYGON_SMOOTH);
	
	CAM.lastxy[0] = CAM.lastxy[1] = 0.0;
	  CAM.spin[0] =   CAM.spin[1] =  CAM.spin[2] = 0.0;
	 CAM.trans[0] =  CAM.trans[1] = CAM.trans[2] = 0.0;
	
}

void reshape(int w, int h)
{
	GLfloat fAspect;

	// Prevent a divide by zero
	if(h == 0)
		h = 1;

	// Set Viewport to window dimensions
    glViewport(0, 0, w, h);

	// Calculate aspect ratio of the window
	fAspect = (GLfloat)w/(GLfloat)h;

	// Set the perspective coordinate system
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Field of view of 45 degrees, near and far planes 1.0 and 425
	gluPerspective(45.0f, fAspect, 0.1, 425.0);

	// Modelview matrix reset
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

int main(int argc, char* argv[])
{
	data = fopen( "/home/pafluxa/Projects/nbodygpu/output/coord.txt", "r" );
	
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	
	glutInitWindowSize(SCREEN_X, SCREEN_Y);
	glutCreateWindow("Point Sprite Night Lights");
	
	glutReshapeFunc(reshape);
	glutDisplayFunc(renderScene);
	
	initGL();
	
	// Tell GLUT to use the method "keyPressed" for key presses  
	glutKeyboardFunc(keyPressed);
	// Tell GLUT to use the method "keyUp" for key up events 
	glutKeyboardUpFunc(keyUp);    
	
	glutMouseFunc        (zoomFunction);
	glutPassiveMotionFunc(cameraMovementFunction);
	glutMainLoop();

	return 0;
}
